package zilvic.game.room.entity.actor;

import java.awt.Graphics;
import org.duncan.Math2D.Math2D;
import org.duncan.Math2D.Point2D;
import zilvic.game.Game;
import zilvic.game.room.entity.Collidable;
import zilvic.game.room.entity.Entity;
import zilvic.game.room.entity.Interactable;
import zilvic.resources.ByteMagic;
import zilvic.resources.sprites.ActorSprite;

/**
 *
 * @author Duncan
 */
public abstract class Actor extends Entity implements Collidable, Interactable {
    private boolean moving = false;
    private byte direction = DIRECTION_DOWN;
    private double speed   = 1d;
    private boolean solid  = false;
    private ActorSprite sprite;

    public final static byte DIRECTION_UP    = 0;
    public final static byte DIRECTION_LEFT  = 1;
    public final static byte DIRECTION_DOWN  = 2;
    public final static byte DIRECTION_RIGHT = 3;

    public final static double[] DIRECTION_RADIANS = {
        Math.PI / 4 * 3, Math.PI, Math.PI / 2, 0d};

    public Actor(Point2D position) {
        super (position);
    }

    public Actor setMoving(boolean moving) {
        this.moving = moving;
        return this;
    }

    public Actor setDirection(byte direction) {
        this.direction = direction;
        return this;
    }

    public Actor setSprite(ActorSprite sprite) {
        this.sprite = sprite;
        return this;
    }

    public void requestDirection() {
        
    }

    @Override
    public void step(long delta) {
        super.step(delta);
        if (onGrid()) {
            requestDirection();
            Point2D c = getPosition().clone().add(
                    Math2D.lengthDir(Game.GRID_SIZE,
                    DIRECTION_RADIANS[getDirection()]));
            if (Game.getInstance().getRoom().collideWith(c))
                setMoving(false);
        }
        if (isMoving()) {
            getPosition().add(Math2D.lengthDir(getSpeed() * delta, Actor.DIRECTION_RADIANS[getDirection()]));
        }
    }

    /**
     * Returns true if the character is allowed to pass this actor. Else false.
     * @return True if this is solid. Else false.
     */
    public boolean collideWith() {
        return (!solid);
    }

    /**
     * Is called whenever the character is trying to interact with this. Usually
     * called when the player is attempting to attack something.
     */
    public abstract void firstHandInteraction();

    /**
     * Is called whenever the character is trying to interact with this.
     * Usually called when the player is attmepting to open something, talk to
     * someone etc.
     */
    public abstract void secondHandInteraction();

    /**
     * Sets the maximum moving speed of the actor.
     * @param speed The maximum speed.
     * @return A reference to this.
     */
    public Actor setSpeed(double speed) {
        this.speed = speed;
        return this;
    }

    public boolean isMoving() {
        return moving;
    }

    public byte getDirection() {
        return direction;
    }

    public ActorSprite getSprite() {
        return sprite;
    }

    public boolean hasSprite() {
        return (getSprite() != null);
    }

    public double getSpeed() {
        return speed;
    }

    public boolean onGrid() {
        return (getPosition().getX() / ((double) Game.GRID_SIZE) == (int) (getPosition().getX() / ((double) Game.GRID_SIZE))
            &&  getPosition().getY() / ((double) Game.GRID_SIZE) == (int) (getPosition().getY() / ((double) Game.GRID_SIZE)));
    }

    @Override
    public void paint(Graphics g, byte step) {
        if (hasSprite()) {
            getSprite().draw(g, getPosition(), step, getDirection());
        }
    }

    /**
     * The format is as follows.
     * 0:  The entity.
     * 1:  Solid?  (boolean).
     * 2:  Moving? (boolean).
     * 3:  Direction (byte).
     * 4:  Speed (double).
     * 5:  Sprite
     * @return This actor in bytes.
     */
    @Override
    public byte[] toBytes() {
        return ByteMagic.compressBytes(new byte[][] {
            super.toBytes(),
            ByteMagic.booleanToBytes(solid),
            ByteMagic.booleanToBytes(moving),
            new byte[] {direction},
            ByteMagic.doubleToBytes(speed),
            sprite.toBytes()});
    }

    @Override
    public void fromBytes(byte[] bytes) {
        byte[][] u = ByteMagic.uncompressBytes(bytes);
        super.fromBytes(u[0]);
        solid = ByteMagic.bytesToBoolean(u[1]);
        moving = ByteMagic.bytesToBoolean(u[2]);
        direction = u[3][0];
        speed = ByteMagic.bytesToDouble(u[4]);
        sprite.fromBytes(u[5]);
    }
}